<?php

/**
 * Description of x10_Observer
 *
 * @author prometheus
 */
class x10_Observer extends x10_Manager
{
	public $xName = 'Megfigyelt objektum: ';

	protected $object;

	/**
	 * Konstruktor.
	 * @param x10_Config $config
	 * @throws x10_E_Observer_ObservedObjectIsNotObject
	 * @throws x10_E_Observer_ObservedObjectUndefined
	 */
	public function __construct(x10_Config $config)
	{
		parent::__construct($config);
		$this->registerProvider(new x10_Provider_Descriptor('x10_Observer_MethodProvider'));
		$this->object = $this->config->getConfigKey('object');
		$append = '???';
		if (isset($this->object))
		{
			if (is_object($this->object))
			{
				if (isset($this->object->xName))
				{
					$append = $this->object->xName;
				}
				else
				{
					$append = get_class($this->object);
				}
			}
			else
			{
				throw new x10_E_Observer_ObservedObjectIsNotObject($this->xName);
			}
		}
		else
		{
			throw new x10_E_Observer_ObservedObjectUndefined($this->xName);
		}
		$this->xName .= $append;
	}

	/**
	 * Ellenőrzi egy felfűzni való elem érvényességét.
	 *
	 * @param x10_Provider $item
	 * @param Bool $enableExceptions
	 * @return Bool
	 * @throws x10_E_Manager_InvalidItemProvider
	 * @throws x10_E_Manager_InvalidItemProviderType
	 * @throws x10_E_Observer_ProviderMisconfigured
	 */
	public function validateItem(&$item, $enableExceptions=FALSE)
	{
		$result = parent::validateItem($item, $enableExceptions);
		if ($result)
		{
			$result = $this->validateItemMethodProvider($item);
			if (!$result and $enableExceptions)
			{
				throw new x10_E_Observer_ProviderMisconfigured($this->xName);
			}
		}
		return $result;
	}

	/**
	 * Ellenőrzi, hogy a metódus szolgáltató paraméterei stimmelnek-e.
	 * @param x10_Provider $item
	 * @return Bool
	 */
	protected function validateItemMethodProvider(&$item)
	{
		$result = TRUE;
		$errors = array();
		if (!isset($item->methodName))
		{
			$errors[] = 'Nincs meghatározva a megfigyelt metódus.';
		}
		if (!isset($item->callback))
		{
			$errors[] = 'Nincs meghatározva eseménykezelő metódus.';
		}
		if (!$item->onBefore and !$item->onAfter)
		{
			$errors[] = 'A megfigyelés sosem fog megtörténni, mert nincs megadva végrehajtási szempont (onBefore vagy onAfter).';
		}

		if (count($errors) > 0)
		{
			$result = implode(' ', $errors);
		}

		return $result;
	}

	/**
	 * Megkeresi a kapott metódushoz tartozó leírókat és visszatér velük.
	 * Amennyiben a keresett metódus nem található, FALSE-szal tér vissza.
	 * @param String $methodName
	 * @return Bool|x10_Observer_MethodProvider FALSE vagy [0..n]{object}
	 */
	protected function findObservedMethod($methodName)
	{
		$result = array();
		$providers = (array)$this;
		foreach ($providers as $methodProvider)
		{
			if ($methodProvider->methodName == $methodName)
			{
				$result[] = $methodProvider;
			}
		}
		return count($result) > 0? $result : FALSE;
	}

	/**
	 * Hívás elkapás.
	 * Megkísérli meghívni a megfigyelt objektum azonos nevű metódusát. A hívás
	 * előtt lefuttat minden, ehhez a metódushoz rendelt onBefore kezelőt.
	 * Amennyiben egy onBefore kezelő FALSE-szal tér vissza, a hívási lánc
	 * megszakad - ellenkező esetben lefut a megfigyelt objektum metódusa, majd
	 * azt követően lefutnak az onAfter kezelők is.
	 *
	 * @param String $methodName
	 * @param Array $args
	 * @return mixed A megfigyelt objektum metódusának visszatérése
	 */
	public function __call($methodName, $args)
	{
		$result = NULL;
		$callable = is_callable(array($this->object, $methodName));
		if (!$callable)
		{
			throw new x10_E_Observer_ObservedMethodUnaccessible($this->xName, $methodName);
		}

		if (($methodProviderList = $this->findObservedMethod($methodName)) !== FALSE)
		{
			foreach ($methodProviderList as $methodProvider)
			{
				if ($methodProvider->onBefore)
				{
					if (!is_callable($methodProvider->callback))
					{
						throw new x10_E_Observer_CallbackMethodUnaccessible($this->xName, (string)$methodProvider);
					}
					
					$callable = call_user_func_array($methodProvider->callback, $args);
				}
				if (!$callable) break;
			}
			
			if ($callable !== FALSE)
			{
				$result = call_user_func_array(array($this->object, $methodName), $args);
				$callable = $result;
			}
			
			foreach ($methodProviderList as $methodProvider)
			{
				if ($callable !== FALSE and $methodProvider->onAfter)
				{
					if (!is_callable($methodProvider->callback))
					{
						throw new x10_E_Observer_CallbackMethodUnaccessible($this->xName, (string)$methodProvider);
					}

					$callable = call_user_func_array($methodProvider->callback, $args);
				}
			}
		}
		else
		{
			$result = call_user_func_array(array($this->object, $methodName), $args);
		}
		
		return $result;
	}
}

?>